package com.storytron.swat.util;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.undo.UndoManager;

import com.storytron.swat.Swat;

/**
 * This is a class for undoing slider actions.
 * Changes done in intervals smaller than 750 ms are merged
 * into a single undo & redo action.
 * <p>
 * The {@link UndoableSlider} registers as a ChangeListener of a
 * slider. Whenever the slider is changed, proper {@link UndoableAction}s are
 * registered with the {@link UndoManager}.   
 * <p>
 * This class uses {@link #clone()} to make independent undoable actions. 
 * So, if you subclass this class you may like to deepcopy any of its fields.</p> 
 * <p>
 * Here's an example:
 * <pre>
 * 		new UndoableSlider(swat,sliderForATraitValue){
 *			Actor actor;
 *			@Override
 *			public int init() {
 *				actor = ActorEditor.this.mActor;
 *				return (int)(actor.get(t)*50.0f+49);
 *			}
 *			
 *			@Override
 *			public void setValue(int value) {
 *				actor.set(t,(float)((value-49) / 50.0));
 *			}
 *			@Override
 *			public void undoRedoExecuted() { showActors(actor);	}
 *
 *			public String getPresentationName() { return "change "+t.name()+" of "+actor.getLabel();	}
 *		};
 *</pre>
 * The {@link #init()} method is called at the time the an {@link UndoableAction} is to be registered
 * because the slider changed. Then the {@link #setValue(int)} will be called
 * whenever the action is undone or redone. After that, {@link #undoRedoExecuted()} will be
 * called so the editor containing the slider has the chance to reflect the changes. 
 * */
public abstract class UndoableSlider implements ChangeListener, Cloneable {
	/**
	 * Creates a SliderUndo which registers on <code>swat</code> UndoableActions
	 * generated by the slider <code>sld</code>. 
	 * */
	public UndoableSlider(Swat swat,Swat.Slider sld){
		slider = sld;
		this.swat = swat;
		slider.getModel().addChangeListener(this);
	}
	/**
	 * This is called every time the slider is changed. 
	 * */
	public abstract int init();
	/**
	 * This is called for setting a value on the model.
	 * It is also called after {@link #init()} when the slider changes.
	 * You don't need to set back the slider state, that's done
	 * automatically for you.
	 * */
	public abstract void setValue(int value);
	/**
	 * This is called to update the GUI after a redo or undo.
	 * You don't need to set back the slider state, that's done
	 * automatically for you.
	 * */
	public abstract void undoRedoExecuted();
	/** 
	 * Override this if you want to set a name for the generated actions.
	 * This is called whenever an undoable action is generated. 
	 * */
	public String getPresentationName() { return "";	}

	private Swat.Slider slider;
	private Swat swat;
	private long keyWhen=0;
	private SliderUndoableAction current = null; 

	public void stateChanged(ChangeEvent e) {
		if (!slider.isUserInput())	return;
		
		long now = System.currentTimeMillis();
		if (now-keyWhen<750) {
			keyWhen = now;
			current.newValue = slider.getValue();
			current.redo();
		} else {
			keyWhen = now;
			try {
				// Clone the state so different operations on the same slider
				// do not interfere with each other.
				UndoableSlider cloned = (UndoableSlider)super.clone();
				current = cloned.new SliderUndoableAction(swat,cloned.init(),slider.getValue());
			} catch (CloneNotSupportedException exc){
				System.out.println("SliderUndo.stateChanged: Clone for SliderUndo not supported.");
			}				
		}
	}

	private class SliderUndoableAction extends UndoableAction {
		private static final long serialVersionUID = 1L;
		public int oldValue;
		public int newValue;
		public SliderUndoableAction(Swat swat,int oldValue,int newValue) {
			super(swat,false,UndoableSlider.this.getPresentationName());
			this.oldValue = oldValue;
			this.newValue = newValue;
			UndoableSlider.this.setValue(newValue);
		}
		@Override
		public void myRedo() {						
			UndoableSlider.this.setValue(newValue);
			slider.mSetValue(newValue);
			UndoableSlider.this.undoRedoExecuted();
		}
		@Override
		public void myUndo() {
			UndoableSlider.this.setValue(oldValue);
			slider.mSetValue(oldValue);
			UndoableSlider.this.undoRedoExecuted();
		}
	}
}
